#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>


static void print_err(char *str, int line, int err_no) {
    if(str) {
        printf("line %d %s: %s\n", line, str, strerror(err_no));
        exit(-1);
    }
}

int main() {
    /* 打开源文件 */
    int srcfd = open("./srcfile", O_RDONLY);
    if (srcfd < 0) print_err("open source file fail", __LINE__, errno);

    /* 打开目标文件 */
    int dstfd = open("./dstfile", O_RDWR | O_CREAT | O_TRUNC, 0664);
    if (dstfd < 0) print_err("open dest file fail", __LINE__, errno);

    /* 获取源文件状态属性 */
    struct stat statbuf = {0};
    fstat(srcfd, &statbuf);
    int len = statbuf.st_size; // 文件总大小
    
    printf("source file size = %d\n", len);

    /* 映射源文件, 返回映射到的虚拟空间首地址 */
    void *srcaddr = mmap(NULL, len, PROT_READ, MAP_SHARED, srcfd, 0);
    if (srcaddr == (void *)-1) print_err("mmap dstfile fail", __LINE__, errno);

    /* 映射目标文件, 返回映射到的虚拟空间首地址 */
    ftruncate(dstfd, len); // 扩展目标文件到源文件大小,因为mmap无法映射到长度为0的文件
    void *dstaddr = mmap(NULL, len, PROT_WRITE, MAP_SHARED, dstfd, 0);
    // MAP_SHARED 与其他进程共享映射区域 
    // 如果设置了PROT_WRITE, 映射的文件必须以O_RDWR方式打开,不能以O_RDONLY或O_WRONLY
    
    if (dstaddr == (void *)-1) print_err("mmap dstfile fail", __LINE__, errno);

    /* 将源文件数据复制到目标文件 
     * mempcy 要求两个地址段无重叠区域, 否则可能产生脏数据
     * */
    memcpy(dstaddr, srcaddr, len);

    /* 获取目标文件大小 */
    fstat(dstfd, &statbuf);
    len = statbuf.st_size;
    printf("destination file size = %d\n", len);

    /* 取消映射 */
    munmap(dstaddr, len);
    munmap(srcaddr, len);

    return 0;
}


